package ajjj.kiviq.beans;

import java.util.*;
import ajjj.kiviq.entities.*;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

/**
 *
 * @author orjohan
 * 
 * Updated 2011-10-11 by orjohan & jeklund
 * -Added result method for checking 
 * result of test
 * 
 * Updated 2011-10-11 by jonatan
 * - atQuestion is set to 0 when resetting
 * the category
 * 
 * Updated 2011-10-12 by jonatan
 * - Fixed navigation if no alternative selected
 * bug
 * 
 * Updated 2011-10-13 by jonatan
 * - Added isAlternativeSelected method that may
 * be used when navigating backwards, changed so
 * that the selected radiobutton value is binded
 * 
 * Updated 2011-10-14 by jeklund
 * Moved getQuestionAlternative to this class 
 * (TestFacade was not really a facade class)
 * 
 */
@ManagedBean
@SessionScoped
public class TestBean {

    private Category category;
    private Map<Question, Alternative> answers;
    private int atQuestion;
    private String error;
    private int correct = 0;
    private int incorrect = 0;
    private Long selectedAlternative;

    /** Creates a new instance of TestBean */
    public TestBean() {
    }

    /**
     * Called by the EJB container, initializes
     * this bean. This might as well be done in
     * the constructor as this does not necessarily have
     * to be a managed bean...
     */
    @PostConstruct
    private void init() {
	answers = new HashMap<Question, Alternative>();
	atQuestion = 0;
	selectedAlternative = null;
	error = "";
    }

    /**
     * Get the id of the currently selected
     * alternative
     * @return The id
     */
    public Long getSelectedAlternative() {
	return selectedAlternative;
    }

    /**
     * Set the id of the currently selected
     * alternative
     * @param selectedAlternative The id
     */
    public void setSelectedAlternative(Long selectedAlternative) {
	this.selectedAlternative = selectedAlternative;
    }

    /**
     * Check whether a certain alternative is
     * the selected one
     * @param a The Alternative to check
     * @return True if it is the currently selected
     * alternative, false otherwise
     */
    public boolean isAlternativeSelected(Alternative a) {
	return isAlternativeSelected(getCurrentQuestion(), a);
    }

    /**
     * Check whether a certain alternative is
     * the selected one
     * @param q The Question that contains the Alternative
     * @param a The Alternative to check
     * @return True if it is the currently selected
     * alternative, false otherwise
     */
    public boolean isAlternativeSelected(Question q, Alternative a) {
	return a != null && answers.get(q) != null && a.equals(answers.get(q));
    }

    /**
     * Get the current error message (if any)
     * @return The current error message, or an
     * empty string if there is none
     */
    public String getError() {
	String err = error;
	error = "";
	return err;
    }

    /**
     * Navigate to the previous question
     */
    public void prev() {
	if (isNotFirstQuestion()) {
	    atQuestion--;
	    Alternative selAlt = answers.get(getCurrentQuestion());
	    if (selAlt != null) {
		setSelectedAlternative(selAlt.getId());
	    }
	}
    }

    /**
     * Navigate to the next question
     * @return A preferred navigation string
     */
    public String next() {
	//Clears answers incase user did not use the 'Back to Category' button
	if (isFirstQuestion()) {
	    answers.clear();
	}

	Alternative a = getQuestionAlternative(selectedAlternative, getCurrentQuestion());

	if (a != null) {
	    answers.put(getCurrentQuestion(), a);
	} else {
	    error = "Please select an alternative";
	    return null;
	}
	if (isNotLastQuestion()) {
	    atQuestion++;
	    return null; //no navigation rule in faces-config here
	} else {
	    result();
	    return "result.jsf?faces-redirect=true";
	}

    }

    /**
     * Calculates the correct and incorrect properties,
     * based on the current state of questions and answers
     */
    public void result() {
	correct = 0;
	incorrect = 0;

	for (Map.Entry<Question, Alternative> a : answers.entrySet()) {
	    if (a.getValue().isRight()) {
		correct++;
	    } else {
		incorrect++;
	    }
	}

    }

    /**
     * Clear the answers and navigate back to the
     * categories
     * @return A navigation string pointing back
     * to the categories page
     */
    public String goBack() {
	answers.clear();
	return "toCat";
    }

    /**
     * Check whether we are currently at the last question
     * @return True if we are at the last question,
     * false otherwise
     */
    public boolean isLastQuestion() {
	return atQuestion == category.getQuestions().size() - 1;
    }

    /**
     * Check whether we are currently at the first question
     * @return True if we are at the first question,
     * false otherwise
     */
    public boolean isFirstQuestion() {
	return atQuestion == 0;
    }

    /**
     * Check whether we are currently NOT at the last question,
     * this method is used as a convenient alternative to
     * !isLastQuestion()
     * @return False if we are at the last question,
     * true otherwise
     */
    public boolean isNotLastQuestion() {
	return !isLastQuestion();
    }

    /**
     * Check whether we are currently NOT at the first question,
     * this method is used as a convenient alternative to
     * !isFirstQuestion()
     * @return False if we are at the first question,
     * true otherwise
     */
    public boolean isNotFirstQuestion() {
	return !isFirstQuestion();
    }

    /**
     * Check how many questions there are in the
     * category
     * @return The cardinality of questions in the category
     */
    public int numQuestions() {
	return category.getQuestions().size();
    }

    /**
     * Check what question we are at
     * @return The 1-based index of the current question
     */
    public int at() {
	return atQuestion + 1;
    }

    /**
     * Get the current question
     * @return The current question
     */
    public Question getCurrentQuestion() {
	return category.getQuestions().get(atQuestion);
    }

    /**
     * Get the alternative of a certain question that has
     * a given ID
     * 
     * @param id The id of the Alternative
     * @param owner The Question that contains the Alternative
     * @return The Alternative if found, false otherwise
     */
    public Alternative getQuestionAlternative(Long id, Question owner) {
	try {
	    for (Alternative a : owner.getAlternatives()) {
		if (a.getId().equals(id)) {
		    return a;
		}
	    }

	    return null;
	} catch (Exception e) {
	    return null;
	}
    }

    /**
     * Get the Category that is currently being tested,
     * probably not manipulation safe
     * @return The Category
     */
    public Category getCategory() {
	return category;
    }

    /**
     * Set the Category that should be tested,
     * this resets and initializes this TestBean
     * @param category The Category to test
     */
    public void setCategory(Category category) {
	this.category = category;
	atQuestion = 0;
	selectedAlternative = null;
	this.category.shuffleQuestions();
	for (Question q : this.category.getQuestions()) {
	    q.shuffleAlternatives();
	}
    }

    /**
     * Get a String percentage representation
     * of the results of the test
     * 
     * @return A percentage of correct / numQuestions
     */
    public String getPercentage() {
	return String.format("%d",
		Math.round(100.0 * correct / (correct + incorrect)));
    }

    /**
     * Get the number of correctly answered questions
     * @return The number of correctly answered questions
     */
    public int getCorrect() {
	return correct;
    }

    /**
     * Set the number of correctly answered questions
     * @param correct The number of correctly answered questions
     */
    public void setCorrect(int correct) {
	this.correct = correct;
    }

    /**
     * Get the number of incorrectly answered questions
     * @return The number of incorrectly answered questions
     */
    public int getIncorrect() {
	return incorrect;
    }

    /**
     * Set the number of incorrectly answered questions
     * @param correct The number of incorrectly answered questions
     */
    public void setIncorrect(int incorrect) {
	this.incorrect = incorrect;
    }

    /**
     * Get a map containing the answers
     * @return The answers
     */
    public Map<Question, Alternative> getAnswers() {
	return answers;
    }

    /**
     * Set the map of answers
     * @param answers The answers
     */
    public void setAnswers(Map<Question, Alternative> answers) {
	this.answers = answers;
    }
}